from math import e, log

from cached_property import cached_property

from settings import CONSTANTS
from stack import Stack


def ln(x):
    return log(x, e)


class Section:
    def __init__(self, radius: int, slope_degree: float, start: Stack, end: Stack, _type=None, influence_coefficient=1):
        self.start = start  # 起点桩
        self.end = end  # 终点桩
        if getattr(start, 'name', None) is not None:
            self.name = start.name
        self.radius = int(radius)
        self.slope_degree = float(slope_degree)
        self.type = _type
        if _type is None:
            self.compute_type()
        self.previous_section = None
        self.latter_section = None
        self.length = abs(self.end.meters - self.start.meters)
        self.reverse = bool(self.start.meters > self.end.meters)
        self.influence_coefficient = influence_coefficient

    def __len__(self):
        return self.length

    def compute_type(self):
        """
        计算路段类型
        """
        _type = ''
        if self.radius > 1000:
            if abs(self.slope_degree) >= 3.0:
                _type = 'slope'
            else:
                _type = 'line'
        else:
            if abs(self.slope_degree) >= 3.0:
                _type = 'curve_slope'
            else:
                _type = 'curve'
        self.type = _type

    @property
    def v_out_l(self):
        return self._v_out_l * self.influence_coefficient

    @v_out_l.setter
    def v_out_l(self, value):
        v_out_l = min(value, CONSTANTS['V_E_L'])
        v_out_l = max(v_out_l, CONSTANTS['V_MIN_L'])
        self._v_out_l = v_out_l

    @property
    def v_out_b(self):
        return self._v_out_b * self.influence_coefficient

    @v_out_b.setter
    def v_out_b(self, value):
        v_out_b = min(value, CONSTANTS['V_E_B'])
        v_out_b = max(v_out_b, CONSTANTS['V_MIN_B'])
        self._v_out_b = v_out_b

    @property
    def v_in_l(self):
        if self.previous_section is None:
            return CONSTANTS['V_0_L']
        else:
            return self.previous_section.v_out_l

    @property
    def v_in_b(self):
        if self.previous_section is None:
            return CONSTANTS['V_0_B']
        else:
            return self.previous_section.v_out_b

    @cached_property
    def v_gradient_l(self):
        return self.v_diff_l / self.length * 1000

    @cached_property
    def v_gradient_b(self):
        return self.v_diff_b / self.length * 1000

    @cached_property
    def v_diff_l(self):
        return self.v_out_l - self.v_in_l

    @cached_property
    def v_diff_b(self):
        return self.v_out_b - self.v_in_b

    def compute_line(self):
        for flag in ['l', 'b']:
            length = self.length
            v_in = getattr(self, 'v_in_' + flag)
            if length <= 200:
                v_out = v_in
            else:
                upper_flag = bytes(flag, encoding="ASCII").upper().decode()
                a_min = CONSTANTS['A_MIN_' + upper_flag]
                a_max = CONSTANTS['A_MAX_' + upper_flag]
                v_e = CONSTANTS['V_E_' + upper_flag]
                a = a_min + (a_max - a_min) * (1 - v_in / v_e)
                v_out = 3.6 * (((v_in / 3.6) ** 2 + 2 * a * length) ** 0.5)
            setattr(self, 'v_out_' + flag, v_out)

    def compute_curve(self):
        previous_section_type = self.previous_section.type if self.previous_section is not None else 'line'
        r_back = self.previous_section.radius if self.previous_section is not None else CONSTANTS[
            'LINE_RADIUS']
        v_in_l = self.v_in_l
        latter_section_type = self.latter_section.type if self.latter_section is not None else 'line'
        r_front = self.latter_section.radius if self.latter_section is not None else CONSTANTS[
            'LINE_RADIUS']
        v_in_b = self.v_in_b

        r_now = self.radius

        if previous_section_type == 'line' or previous_section_type == 'slope':
            v_middle_l = -24.212 + 0.834 * v_in_l + 5.729 * ln(r_now)
            v_middle_b = -9.432+0.963*v_in_b+1.522*ln(r_now)
        else:
            v_middle_l = 1.227 + 0.942 * v_in_l + \
                6.19*ln(r_now)-5.959*ln(r_back)
            v_middle_b = -24.472+0.990*v_in_b+3.629*ln(r_now)
        if latter_section_type == 'line' or latter_section_type == 'slope':
            v_out_l = 11.946 + 0.908 * v_middle_l
            v_out_b = 5.217 + 0.926 * v_middle_b
        else:
            v_out_l = -11.299 + 0.936 * v_middle_l - \
                2.060 * ln(r_now)+5.203 * ln(r_front)
            v_out_b = 5.899 + 0.925 * v_middle_b - \
                1.005 * ln(r_now) + 0.329 * ln(r_front)

        self.v_out_l = v_out_l
        self.v_out_b = v_out_b

    def compute_slope(self):
        slope_degree = self.slope_degree if not self.reverse else -self.slope_degree
        if 0 <= slope_degree <= 4:
            v_out_l = self.v_in_l - (self.length/1000)*5
            v_out_b = self.v_in_b - (self.length/1000)*10
        elif slope_degree > 4:
            v_out_l = self.v_in_l - (self.length/1000)*8
            v_out_b = self.v_in_b - (self.length/1000)*20
        elif -4 <= slope_degree < 0:
            v_out_l = self.v_in_l + (self.length/500)*10
            v_out_b = self.v_in_b + (self.length/500)*7.5
        else:
            v_out_l = self.v_in_l + (self.length/500)*20
            v_out_b = self.v_in_b + (self.length/500)*15

        self.v_out_b = v_out_b
        self.v_out_l = v_out_l

    def compute_curve_slope(self):
        previous_section_type = self.previous_section.type if self.previous_section is not None else 'line'
        r_back = self.previous_section.radius if self.previous_section is not None else CONSTANTS[
            'LINE_RADIUS']
        v_in_l = self.v_in_l
        latter_section_type = self.latter_section.type if self.latter_section is not None else 'line'
        r_front = self.latter_section.radius if self.latter_section is not None else CONSTANTS[
            'LINE_RADIUS']
        v_in_b = self.v_in_b

        r_now = self.radius

        i_now_1 = i_now_2 = self.slope_degree if not self.reverse else -self.slope_degree

        if previous_section_type == 'line' or previous_section_type == 'slope':
            v_middle_l = -31.67 + 0.547 * v_in_l + \
                11.71 * ln(r_now) - 0.176 * i_now_1
            v_middle_b = 1.782+0.859*v_in_b+1.196*ln(r_now) - 0.51*i_now_1
        else:
            v_middle_l = 0.750 + 0.802 * v_in_l + \
                2.717*ln(r_now)-0.281*i_now_1
            v_middle_b = 1.798+0.248 * \
                ln(r_now)+0.977*v_in_b-0.133*i_now_1+0.23*ln(r_back)
        if latter_section_type == 'line' or latter_section_type == 'slope':
            v_out_l = 27.294+0.720*v_middle_l-1.444*i_now_2
            v_out_b = 13.490+0.797*v_middle_b-0.6971*i_now_2
        else:
            v_out_l = 1.819 + 0.839 * v_middle_l + \
                1.427 * ln(r_now)+0.782 * ln(r_front) - 0.48*i_now_2
            v_out_b = 26.837 + 0.109 * \
                ln(r_front)-3.039*ln(r_now)-0.594*i_now_2+0.830*v_middle_b

        self.v_out_l = v_out_l
        self.v_out_b = v_out_b

    def compute_tunnel(self):
        if self.length >= 200:
            v_in_l = self.previous_section.v_in_l
            v_in_b = self.previous_section.v_in_b
            self.previous_section.v_out_l = 0.99 * v_in_l - 11.07
            self.previous_section.v_out_b = 0.98 * v_in_b - 6.56
            self.v_out_l = 0.81 * v_in_l + \
                8.22 if self.length > 200 else self.previous_section.v_out_l
            self.v_out_b = 0.85 * v_in_b + \
                3.89 if self.length > 200 else self.previous_section.v_out_b
            self.latter_section.v_out_l = 0.74 * v_in_l + 16.43
            self.latter_section.v_out_b = 0.45 * v_in_b + 42.61
        else:
            self.v_out_b = self.v_in_b
            self.v_out_l = self.v_in_l

    def compute_interchange(self):
        self.v_out_b = self.v_in_b - 5
        self.v_out_l = self.v_in_l - 8

    def compute(self):
        """
        根据类型计算数据
        """
        if self.latter_section is not None and self.latter_section.type == 'tunnel':
            if self.latter_section.length < 200:
                getattr(self, 'compute_'+self.type)()
        elif self.previous_section is not None and self.previous_section.type == 'tunnel':
            if self.previous_section.length < 200:
                getattr(self, 'compute_'+self.type)()
        else:
            getattr(self, 'compute_'+self.type)()

    def serialize(self):
        return {
            'type': self.type,
            'radius': self.radius,
            'slope_degree': self.slope_degree,
            'start': {
                "meters": self.start.meters,
                "stack_number": self.start.stack_number
            },
            'end': {
                "meters": self.end.meters,
                "stack_number": self.end.stack_number
            },
            'length': self.length,
            'v_in_l': self.v_in_l,
            'v_in_b': self.v_in_b,
            'v_out_l': self.v_out_l,
            'v_out_b': self.v_out_b,
            'v_diff_l': self.v_diff_l,
            'v_diff_b': self.v_diff_b,
            'v_gradient_l': self.v_gradient_l,
            'v_gradient_b': self.v_gradient_b
        }


class LowerLevelSection(Section):
    def compute_type(self):
        """
        计算路段类型
        """
        _type = ''
        if self.radius > 600:
            if abs(self.slope_degree) >= 3.0:
                _type = 'slope'
            else:
                _type = 'line'
        else:
            if abs(self.slope_degree) >= 3.0:
                _type = 'curve_slope'
            else:
                _type = 'curve'
        self.type = _type

    def compute_line(self):
        for flag in ['l', 'b']:
            length = self.length
            v_in = getattr(self, 'v_in_' + flag)
            if length <= 100:
                v_out = v_in
            else:
                upper_flag = bytes(flag, encoding="ASCII").upper().decode()
                a_min = CONSTANTS['A_MIN_' + upper_flag]
                a_max = CONSTANTS['A_MAX_' + upper_flag]
                v_e = CONSTANTS['V_E_' + upper_flag]
                a = a_min + (a_max - a_min) * (1 - v_in / v_e)
                v_out = 3.6 * (((v_in / 3.6) ** 2 + 2 * a * length) ** 0.5)
            setattr(self, 'v_out_' + flag, v_out)

    def compute_curve(self):
        v_in_l = self.v_in_l
        r_now = self.radius
        r_front = self.latter_section.radius if self.latter_section is not None else CONSTANTS[
            'LINE_RADIUS']
        v_in_b = self.v_in_b

        r_front_l = min(r_front, r_now*5)
        r_front_b = min(r_front, r_now*4)

        v_middle_l = -244.13 + 0.6 * v_in_l + 40*ln(r_now+500)
        v_middle_b = -80.179 + 0.7*v_in_b + 15*ln(r_now+250)
        v_out_l = -183.092 + 0.7*v_middle_l + 30*ln(r_front_l+500)
        v_out_b = -53.453 + 0.8*v_middle_b + 10*ln(r_front_b+250)

        self.v_out_l = v_out_l
        self.v_out_b = v_out_b

    def compute_curve_slope(self):
        v_in_l = self.v_in_l
        r_front = self.latter_section.radius if self.latter_section is not None else CONSTANTS[
            'LINE_RADIUS']
        i_3 = self.latter_section.slope_degree if self.latter_section is not None else 0  # 弯坡前方的坡度值
        v_in_b = self.v_in_b

        r_now = self.radius
        r_front_l = min(r_front, r_now*5)
        r_front_b = min(r_front, r_now*4)
        slope_degree = i_1 = i_2 = self.slope_degree if not self.reverse else -self.slope_degree

        if slope_degree >= 0:
            v_middle_l = -244.123 + 0.6*v_in_l + 40 * \
                ln(r_now+500)-(600-r_now)*(i_1-3)/600-0.324*i_2
            v_middle_b = -80.179 + 0.7*v_in_b+15 * \
                ln(r_now+250)-1.2*(600-r_now)*(i_1-2)/600-0.106*i_2
            v_out_l = -183.092+0.7*v_middle_l+30 * \
                ln(r_front_l+500)-1.2*(600-r_now)*(i_2-3)/600-0.324*i_3
            v_out_b = -53.453 + 0.8*v_middle_b+10 * \
                ln(r_front_b+250)-1.5*(600-r_now)*(i_2-2)/600-0.324*i_3
        else:
            v_middle_l = -244.123+0.6*v_in_l+40 * \
                ln(r_now+500)-0.6*r_now*(i_1+3)/600-0.324*i_2
            v_middle_b = -80.179+0.7*v_in_b+15 * \
                ln(r_now+250)-0.8*r_now*(i_1+2)/600-0.106*i_2
            v_out_l = -183.092+0.7*v_middle_l+30 * \
                ln(r_front_l+500)-0.8*r_now*(i_2+3)/600-0.324*i_3
            v_out_b = -53.453+0.8*v_middle_b+10 * \
                ln(r_front_b+250)-r_now*(i_2+2)/600-0.106*i_3

        self.v_out_l = v_out_l
        self.v_out_b = v_out_b
